El tutorial mostrado a continuación pretende mostrar algunos de los pasos para realizar Web Scrapping, aunque también se muestran algunos pasos para manipulación (Pandas) y visualización de los datos (Plotly), el enfoque es la extracción de la información de la web.
El web scrapping es un método para conversión de datos en formatos legibles por humanos -- contenidos en sitios web -- a formatos legibles por máquinas. La ejecución de web scrapping requiere conocimientos en los fundamentos de tecnologías web tales como HTML, CSS, y JavaScript. Para realizar web scrapping se debe tener en cuenta sí el sitio web es estático o dinámico, en el primer caso el sitio web es fijo y siempre muestra el mismo contenido para cada usuario, en el segundo caso la página muestra un contenido diferente de acuerdo a la interacción realizada por el usuario.
El lenguaje de programación Python tiene varias librerías para web scrapping como BeautifulSoup, Selenium, Requests, lxml, o Scrapy. Una de las librerías más utilizadas es BeautifulSoup pero sólo se puede utilizar en sitios web con contenidos estáticos, para sitios webs con contenidos dinámicos se debe utilizar librerías como Selenium.
La VigiBase es una base de datos en donde están contenidos los reportes de caso individual de eventos adversos relacionados a medicamentos provenientes de 110 países miembros de la OMS, esta es mantenida por el Centro de Monitoreo de Uppsala (UMC, Uppsala Monitoring Center). La VigiBase actúa como una base de datos de alcance global en el tema de farmacovigilancia, y se podría considerar como la más grande y exhaustiva del mundo.
La base de datos VigiAccess es una base de datos de resumen de VigiBase, agrupada por principio activo, tipo de evento, año de reporte, grupo etáreo, y región de donde proviene el reporte.

El primer paso es la instalación de la librería mediante el comando pip:
!pip install selenium # En un Jupyter Notebook
python -m install pip install selenium # En la línea de comando
El segundo paso es la instalación del driver que es un programa que actúa con una interface con el navegador. Este driver permite la interacción con el sitio web, para este tutorial se utilizó el driver de Google Chrome que se puede encontrar en el sitio web de Chrome Driver
# Carga de paquetes
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
import re
import pandas as pd
import numpy as np
import time
import plotly.express as px
import plotly.graph_objects as go
Tras la carga de los paquetes se deben especificar algunas opciones del driver, como el tamaño de ventana. Así mismo se debe especificar la posición relativa (para Jupyter Notebook) en el sistema de chromedriver.
options = Options()
options.headless = True
options.add_argument("--window-size=1920,1200")
DRIVER_PATH = './chromedriver.exe' # El driver para este ejemplo se encuentra en la misma carpeta de proyecto.
Un primer paso es la definición del driver de Chrome, este se crea a partir de la clase base webdriver, el driver se crea mediante el método Chrome y se debe definir la ubicación en el sistema del Driver (DRIVER_PATH). Mediante el método get se puede abrir el sitio web objetivo de este análisis, y se puede verificar su apertura al imprimir el título.
driver = webdriver.Chrome(executable_path=DRIVER_PATH)
driver.get('http://www.vigiaccess.org/')
print(driver.title)
time.sleep(5)
VigiAccess
Al ejecutar la celda anterior se abre el driver como un clon de Chrome que permite interactuar con el sitio web mediante instrucciones en código.

El acceso a la base se realiza mediante la aceptación de términos y condiciones. En la parte inferior de la página se puede encontrar una casilla de verificación y un botón de acceso. Mediante el método abreviado del teclado Ctrl + Mayus + C se puede hacer una inspección de los elementos de la página, al aplicar el método se abren las herramientas del desarrollador.

Se puede observar que la casilla de verificación tiene el identificador acceptTermsCheckBox, y el botón es el único elemento con el nombre de clase btn. Estos dos aspectos se pueden utilizar para simular el acceso por parte de un usuario.
<!-- Casilla de verificación -->
<input id="acceptTermsCheckBox" type="checkbox" ng-model="acceptTerms" ng-checked="acceptTerms" class="ng-pristine ng-valid">
<!-- Botón de entrada -->
<button type="button" class="btn btn-primary btn-default btn-sm ng-pristine ng-valid" ng-click="showContent()" ng-model="acceptTerms" ng-disabled="!acceptTerms" disabled="disabled">Search database</button>
A continuación, se utiliza con el driver un selector de id (find_element_by_id) y nombre de clase (find_element_by_class_name) de manera respectiva, tras la selección se aplica el método click.
# Recuerde salir de la consola de desarollo antes de su ejecución
driver.find_element_by_id('acceptTermsCheckBox').click()
driver.find_element_by_class_name('btn').click()
time.sleep(5)

A continuación, se debe buscar el principio activo. En la página se observa un formulario de entrada de caracteres, junto a un botón, si se abre la consola de desarrollo se observa que el formulario cuenta con las siguientes especificaciones:
<input autofocus="" type="search" placeholder="Enter tradename of drug" ng-model="drug" ng-change="noInfo()" class="ng-pristine ng-valid">
Para este caso se puede utilizar un selector de tipo xpath (find_element_by_xpath), en donde se tiene en cuenta el tipo de elemento y además se pueden realizar consultas de las propiedades del elemento, para este formulario se hace selección de un elemento de tipo "input" y la propiedad type con valor "search". Tras seleccionar el formulario se envía el string con el nombre del fármaco al formulario.
En el caso del botón se tiene la siguiente información:
<button class="btn btn-primary btn-default btn-sm" ng-click="getDrug(drug)" ng-disabled="drug.length == 0" disabled="disabled">Search</button>
Se puede realizar la selección de un elemento de tipo "button" con la propiedad ng-click en forma de llamada de función de javascript "getDrug(drug)".
# Recuerde salir de la consola de desarollo de Chrome antes de la ejecución de esta celda
drug = 'Ciprofloxacin'
driver.find_element_by_xpath("//input[@type='search']").send_keys(drug)
driver.find_element_by_xpath("//button[@ng-click='getDrug(drug)']").click()
time.sleep(5)

Para este ejemplo se abre el primer nodo de Reacciones Adversas a Fármacos (ADR, Adverse Drug Reactions).
<h4 class="panel-title">
<a class="accordion-toggle" ng-click="toggleOpen()" accordion-transclude="heading">
<span class="pull-left glyphicon ng-scope glyphicon-chevron-right" ng-class="{'glyphicon-chevron-down': accordionState.adrDistributionOpen, 'glyphicon-chevron-right': !accordionState.adrDistributionOpen}"></span>
<span class="ng-scope">Adverse drug reactions (ADRs)</span>
</a>
</h4>
Como se observa en la figura tenemos varios contenedores para cada sección de interés, todos los contenedores contienen un texto de hipervínculo de tipo "a" que tienen la clase "accordion-toggle". La diferencia entre los contenedores es un elemento de tipo "span" con el contenido de texto de cada sección de interés.
La selección del elemento se puede realizar mediante el método find_element_by_xpath, con la siguiente consulta:
"//a[@class='accordion-toggle' and ./span[contains(text(),'Adverse drug reactions (ADRs)')]]"
# Recuerde salir de la consola de desarollo de Chrome antes de la ejecución de esta celda
# Apertura de acordeón con Categorías de RAM
driver.find_element_by_xpath("//a[@class='accordion-toggle' and ./span[contains(text(),'Adverse drug reactions (ADRs)')]]").click()
RAM_categories = driver.page_source
A continuación, se deben abrir los nodos para cada clase de reacción adversa:

Esto se puede lograr al realizar click en todos los elementos de tipo "span" con clase ng-binding y ng-scope. Se debe dar un tiempo de espera de 1 segundo entre cada item.
# Apertura de nodos principales
category_ls = driver.find_elements_by_xpath("//span[@class='ng-binding ng-scope']")
for i in category_ls:
i.click()
time.sleep(1)
A continuación se da click en todos los elementos de tipo "a" y clase ng-scope. Al dar click sobre estos elementos se cargan todos las RAM restantes.

more_data_ls = [1]
while(len(more_data_ls) > 0):
more_data_ls = driver.find_elements_by_xpath("//a[@class=\'ng-scope\']")
# print(more_data_ls)
for i in more_data_ls:
try:
i.click()
except:
print(i)
<selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="25d8ff24-c616-4fcc-a09c-97a8cba3ac52")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="4d9bcfa1-947f-4e4e-9dd7-58bafdc67ca2")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="96bb39b4-07fd-41de-82b2-6e45912add29")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="0318348b-8fb9-4c05-93e0-7d664b4e5baa")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="a9eb196c-1336-493c-84ab-f258804f8d58")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="6566b1ef-9e72-4544-a092-0621fd7a5cfc")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="da217c27-99f3-4d68-b4b4-19e8b9ba3d08")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="e1626ba0-3399-48d8-ae11-c7d9f5953800")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="bdba1c6d-84c7-48f1-8e72-458d9638fde3")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="4678ffaa-b571-424c-80a5-3b9b625be9b0")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="baf5cb81-1c9a-4e72-b726-6df4498ae39a")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="de23e6c7-7e5f-4069-9b9b-d3d890a267a3")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="90e9d1f5-ee38-4261-84e3-a7dac032c5c9")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="55d968c6-c5bd-4688-882b-f60bf98e09ff")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="b3401322-063b-4cf8-b8a7-54d1f4232590")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="992ee85b-5077-42f3-9513-70c0c6f9fbdc")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="9bade87b-9b6b-4b1f-ba48-a98bb62d3696")> <selenium.webdriver.remote.webelement.WebElement (session="61521cdadcb82ced60f8635908ca33a2", element="f3f1d1fe-ac22-446f-a9bb-18cfa0ee4522")>
Al realizar todos los procedimientos de manera satisfactoria quedan abiertos todos los nodos y se puede almacenar el contenido del sitio web en este caso en la variable page_source. Se puede cerrar el driver mediante el método quit().
page_source = driver.page_source
driver.quit()
El primer paso es la conversión del archivo a una estructura de datos para manipulación, primero se toma page_source que tiene todos los pares reacción y frecuencia para cada grupo y elemento.
soup = BeautifulSoup(page_source, 'lxml')
Los pares de reacción y frecuencia de reportes se pueden extraer de la estructura al tomar todos los elementos de tipo "span" con clase ng-binding.
ls = [k.text for k in soup.find_all('span', {'class':'ng-binding'})]
ls[:5]
['Blood and lymphatic system disorders (3396)', 'Thrombocytopenia (821)', 'Leukopenia (516)', 'Neutropenia (459)', 'Eosinophilia (322)']
# Conversión de cada registro en una lista con la RAM y la frecuencia
ls_RAM = [[k.strip() for k in re.split('[\(\)]', m) if k!=''] for m in ls]
ls_RAM[:5]
[['Blood and lymphatic system disorders', '3396'], ['Thrombocytopenia', '821'], ['Leukopenia', '516'], ['Neutropenia', '459'], ['Eosinophilia', '322']]
# Conversión a DataFrame
RAM_DF = pd.DataFrame([{'RAM':k[0], 'Number':k[1]} for k in ls_RAM])
Ahora se toma el estado de la página previo a la apertura de los nodos para cada categoría de RAM, contenido en RAM_categories. El archivo en mención contiene sólo la información de las categorías de RAM por sistema fisiológico. A continuación, se extrae el texto de todos los elementos span con clase ng-binding.
ram_cat_ls = [k.text for k in BeautifulSoup(RAM_categories, 'lxml').find_all('span', {'class':'ng-binding'})]
ram_cat_ls[:5]
['Blood and lymphatic system disorders (3396)', 'Cardiac disorders (3390)', 'Congenital, familial and genetic disorders (197)', 'Ear and labyrinth disorders (2014)', 'Endocrine disorders (194)']
ram_cat_ls1 = [[k.strip() for k in re.split('[\(\)]', m) if k!=''] for m in ram_cat_ls]
ram_cat_ls1 = [i[0] for i in ram_cat_ls1]
ram_cat_ls1[:5]
['Blood and lymphatic system disorders', 'Cardiac disorders', 'Congenital, familial and genetic disorders', 'Ear and labyrinth disorders', 'Endocrine disorders']
RAM_DF = RAM_DF.assign(
Category = lambda df: df['RAM'].isin(ram_cat_ls1),
RAM_name = lambda df: ~df['RAM'].isin(ram_cat_ls1)
)
RAM_DF['Category'] = list( map(lambda x,y: y if x else np.nan, RAM_DF['Category'], RAM_DF['RAM']) )
RAM_DF['RAM_name'] = list( map(lambda x,y: y if x else np.nan, RAM_DF['RAM_name'], RAM_DF['RAM']) )
RAM_DF['Category'] = RAM_DF['Category'].fillna(method='ffill')
RAM_DF = RAM_DF.dropna(0).drop(columns='RAM')
RAM_DF
| Number | Category | RAM_name | |
|---|---|---|---|
| 1 | 821 | Blood and lymphatic system disorders | Thrombocytopenia |
| 2 | 516 | Blood and lymphatic system disorders | Leukopenia |
| 3 | 459 | Blood and lymphatic system disorders | Neutropenia |
| 4 | 322 | Blood and lymphatic system disorders | Eosinophilia |
| 5 | 302 | Blood and lymphatic system disorders | Anaemia |
| ... | ... | ... | ... |
| 4977 | 1 | Vascular disorders | Superior vena cava occlusion |
| 4978 | 1 | Vascular disorders | Varicose vein ruptured |
| 4979 | 1 | Vascular disorders | Vascular fragility |
| 4980 | 1 | Vascular disorders | Vein rupture |
| 4981 | 1 | Vascular disorders | Venoocclusive disease |
4955 rows × 3 columns
RAM_DF.to_csv('./RAM_MORFINA.csv', index=False)
if True:
RAM_DF = pd.read_csv('./RAM_MORFINA.csv')
RAM_DF
| Number | Category | RAM_name | |
|---|---|---|---|
| 0 | 821 | Blood and lymphatic system disorders | Thrombocytopenia |
| 1 | 516 | Blood and lymphatic system disorders | Leukopenia |
| 2 | 459 | Blood and lymphatic system disorders | Neutropenia |
| 3 | 322 | Blood and lymphatic system disorders | Eosinophilia |
| 4 | 302 | Blood and lymphatic system disorders | Anaemia |
| ... | ... | ... | ... |
| 4950 | 1 | Vascular disorders | Superior vena cava occlusion |
| 4951 | 1 | Vascular disorders | Varicose vein ruptured |
| 4952 | 1 | Vascular disorders | Vascular fragility |
| 4953 | 1 | Vascular disorders | Vein rupture |
| 4954 | 1 | Vascular disorders | Venoocclusive disease |
4955 rows × 3 columns
Tras las manipulaciones correspondientes, se puede obtener una visión general del perfil de seguridad del fármaco con las reacciones adversas más reportadas a nivel global.
RAM_DF1 = RAM_DF\
.assign(RAM_name1 = lambda df: [i for i in map(lambda x,y: y if x > 15 else 'Others', RAM_DF.Number, RAM_DF.RAM_name)])\
.groupby(by = ['Category', 'RAM_name1']).sum().reset_index()
fig = px.treemap(RAM_DF1, path=['Category', 'RAM_name1'], values='Number',
color='Number',
color_continuous_scale='viridis')
fig.data[0].hovertemplate = 'Id: %{label}<br>N: %{value:.0f}<br>Parent: %{parent}'
fig.show()
Algunos de los pasos previamente mostrados se pueden unir dentro de una función (colocarSitioPrimerNivel) para facilitar las consultas.
def colocarSitioPrimerNivel(driver, drug:str, report:str, url = 'http://www.vigiaccess.org/'):
""" Este método toma el driver, abre la página "vigiaccess.org", realiza la búsqueda del
fármaco, y abre una sección.
Args:
driver::selenium.webdriver.chrome.webdriver.WebDriver
Objeto webdriver de Selenium (para este tutorial Chrome)
drug::str
Fármaco a buscar en la base Vigiaccess
report::str
Tipo de reporte a consultar se pueden utilizar: (1) Adverse drug reactions (ADRs),
(2) Geographical distribution, (3) Age group distribution, (4) Patient sex
distribution, y (5) ADR reports per year.
url::str
Sitio web de VigiAccess
Returns:
Nada, sólo modifica el objeto driver
"""
# Abrir página
driver.get(url)
time.sleep(1)
# Condiciones y términos
driver.find_element_by_id('acceptTermsCheckBox').click()
driver.find_element_by_class_name('btn').click()
time.sleep(1)
# Búsqueda de principio activo
driver.find_element_by_xpath("//input[@type='search']").send_keys(drug)
driver.find_element_by_xpath("//button[@ng-click='getDrug(drug)']").click()
# Apertura de módulo
searchString1 = f"//a[@class='accordion-toggle' and ./span[contains(text(), '{report}')]]"
time.sleep(5)
el = driver.find_element_by_xpath(searchString1)
el.click()
A continuación se muestra la inicialización del webdriver, su manipulación, extracción de la información y cierre.
driver = webdriver.Chrome(executable_path=DRIVER_PATH)
colocarSitioPrimerNivel(driver, drug, 'ADR reports per year')
table = BeautifulSoup(driver.page_source, 'lxml').find('table', {'id':'yearTable'})
driver.quit()
# Conversión de la tabla HTML a un objeto DataFrame
table_rows = table.find_all('tr')
res = []
for tr in table_rows:
td = tr.find_all('td')
row = [tr.text.strip() for tr in td if tr.text.strip()]
if row:
res.append(row)
df = pd.DataFrame(res, columns=["Year", "Count", "Percentage"])
df.head()
| Year | Count | Percentage | |
|---|---|---|---|
| 0 | 2021 | 1007 | 1 |
| 1 | 2020 | 9737 | 8 |
| 2 | 2019 | 12151 | 11 |
| 3 | 2018 | 9900 | 9 |
| 4 | 2017 | 10141 | 9 |
fig = px.scatter(df, x="Year", y="Count", title=f"RAM por año para {drug}o", template='plotly_white')
fig.data[0].update(mode='markers+lines')
fig.update_layout(xaxis_title='Año', yaxis_title="Conteo RAM")
fig.update_traces(marker = {'size':10, 'line': {'width': 2, 'color': 'black'}})
fig.data[0].hovertemplate = 'Año: %{x}<br>Frec.: %{y:.0f}'
fig.show()
A continuación se muestra el mismo procedimiento pero con extracción de información de sexo reportado.
driver = webdriver.Chrome(executable_path=DRIVER_PATH)
colocarSitioPrimerNivel(driver, drug, 'Patient sex distribution')
table = BeautifulSoup(driver.page_source, 'lxml').find('table', {'id':'sexTable'})
driver.quit()
table_rows = table.find_all('tr')
res = []
for tr in table_rows:
td = tr.find_all('td')
row = [tr.text.strip() for tr in td if tr.text.strip()]
if row:
res.append(row)
df = pd.DataFrame(res, columns=['Sex', 'Count', 'Percentage'])
df.head()
| Sex | Count | Percentage | |
|---|---|---|---|
| 0 | Female | 65096 | 57 |
| 1 | Male | 45742 | 40 |
| 2 | Unknown | 4111 | 4 |
# Traducción a español
sexo_dict = {'Female': 'Mujer', 'Male': 'Hombre', 'Unknown': 'Desconocido'}
df1 = df.assign(Sexo = lambda df: df.Sex.apply(lambda x: sexo_dict[x]))
fig = px.pie(df1,
values='Count', names='Sexo', title=f'RAM por sexo para {drug}o',
color_discrete_sequence=px.colors.sequential.RdBu)
fig.data[0].hovertemplate = 'Sexo: %{label}<br>Frecuencia: %{value:.0f}'
fig.show()
driver = webdriver.Chrome(executable_path=DRIVER_PATH)
colocarSitioPrimerNivel(driver, drug, 'Age group distribution')
table = BeautifulSoup(driver.page_source, 'lxml').find('table', {'id':'ageTable'})
driver.quit()
table_rows = table.find_all('tr')
res = []
for tr in table_rows:
td = tr.find_all('td')
row = [tr.text.strip() for tr in td if tr.text.strip()]
if row:
res.append(row)
df = pd.DataFrame(res, columns=['Age Group', 'Count', 'Percentage'])
df.head()
| Age Group | Count | Percentage | |
|---|---|---|---|
| 0 | 0 - 27 days | 116 | 0 |
| 1 | 28 days to 23 months | 352 | 0 |
| 2 | 2 - 11 years | 1214 | 1 |
| 3 | 12 - 17 years | 1874 | 2 |
| 4 | 18 - 44 years | 32535 | 28 |
edad_dict = {
'0 - 27 days': ['0 - 27 días', 'Neonato'],
'28 days to 23 months': ['28 días a 23 meses', 'Lactante'],
'2 - 11 years': ['2 a 11 años', 'Infante'],
'12 - 17 years': ['12 a 17 años', 'Adolescentes'],
'18 - 44 years': ['18 a 44 años', 'Adultos jóvenes'],
'45 - 64 years': ['45 a 64 años', 'Adultos'],
'65 - 74 years': ['65 a 74 años', 'Edad media'],
'≥ 75 years': ['≥ 75 años', 'Ancianos'],
'Unknown': ['Desconocido', 'Desconocido']
}
df1 = df.assign(
Grupo_Edad = lambda df: df.loc[:, 'Age Group'].apply(lambda x: edad_dict[x][0]),
Denom_Edad = lambda df: df.loc[:, 'Age Group'].apply(lambda x: edad_dict[x][1])
)
fig = px.bar(df1,
x='Count', y='Grupo_Edad', title=f'RAM por grupo etáreo para {drug}o',
color = 'Denom_Edad',
color_discrete_sequence=px.colors.sequential.RdBu,
orientation='h',
template='plotly_white',
text = 'Denom_Edad')
fig.update_layout(xaxis_title='Frecuencia',
yaxis_title="Grupo de edad",
legend_title_text='Grupo etáreo')
fig.update_traces(marker = {'line_color': 'black', 'line_width': 1.5},
hovertemplate = 'Grupo edad: %{y}<br>Conteo: %{x:.0f}')
fig.show()
driver = webdriver.Chrome(executable_path=DRIVER_PATH)
colocarSitioPrimerNivel(driver, drug, 'Geographical distribution')
table = BeautifulSoup(driver.page_source, 'lxml').find('table', {'id':'continentTable'})
driver.quit()
table_rows = table.find_all('tr')
res = []
for tr in table_rows:
td = tr.find_all('td')
row = [tr.text.strip() for tr in td if tr.text.strip()]
if row:
res.append(row)
df = pd.DataFrame(res, columns=['Continent', 'Count', 'Percentage'])
df.head()
| Continent | Count | Percentage | |
|---|---|---|---|
| 0 | Africa | 2000 | 2 |
| 1 | Americas | 34327 | 30 |
| 2 | Asia | 44548 | 39 |
| 3 | Europe | 32078 | 28 |
| 4 | Oceania | 1996 | 2 |
gapminder = px.data.gapminder()\
.loc[:, ['country', 'continent', 'iso_alpha']].drop_duplicates()\
.merge(df, how='left', left_on='continent', right_on='Continent')
# gapminder.join(how='left')
gapminder
| country | continent | iso_alpha | Continent | Count | Percentage | |
|---|---|---|---|---|---|---|
| 0 | Afghanistan | Asia | AFG | Asia | 44548 | 39 |
| 1 | Albania | Europe | ALB | Europe | 32078 | 28 |
| 2 | Algeria | Africa | DZA | Africa | 2000 | 2 |
| 3 | Angola | Africa | AGO | Africa | 2000 | 2 |
| 4 | Argentina | Americas | ARG | Americas | 34327 | 30 |
| ... | ... | ... | ... | ... | ... | ... |
| 137 | Vietnam | Asia | VNM | Asia | 44548 | 39 |
| 138 | West Bank and Gaza | Asia | PSE | Asia | 44548 | 39 |
| 139 | Yemen, Rep. | Asia | YEM | Asia | 44548 | 39 |
| 140 | Zambia | Africa | ZMB | Africa | 2000 | 2 |
| 141 | Zimbabwe | Africa | ZWE | Africa | 2000 | 2 |
142 rows × 6 columns
fig = px.choropleth(gapminder, locations='iso_alpha', color='Count', projection='natural earth',
color_continuous_scale=px.colors.sequential.Plasma,
title=f'RAM por origen de reporte para {drug}o',
custom_data = ['continent', 'country', 'Count'])
fig.update_layout(legend_title_text='Conteo')
fig.update_traces(
hovertemplate = 'Continente: %{customdata[0]}<br>País: %{customdata[1]}<br>Conteo: %{customdata[2]}')
fig.show()
En este tutorial vimos como realizar web scrapping dinámico en un sitio web de reporte globales de reacciones adversas, así mismo se muestra una propuesta para la transformación y visualización de estos datos. Una desventaja de los métodos de web scrapping dinámicos es que son relativamente demorados, ya que el driver tiene que hacer varias interacciones con la páginas, sin embargo es un método muy eficiente si se le compara con una extracción manual de la información.